/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.server.servlet.book.epub;

import java.io.IOException;
import java.io.OutputStream;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import offset.nodes.client.model.SaxUtil;
import offset.nodes.client.virtual.model.jcr.PrintHandler;
import offset.nodes.server.servlet.book.BookInfo;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

/**
 *
 * @author Walter L�tgenau
 */
public class Opf {

    String title = null;
    String id = null;
    Locale locale = null;
    BookInfo bookInfo;
    public static final String OPF_BUNDLE = "/offset/nodes/server/view/ApplicationResource";

    class ManifestEntry {

        String id;
        String href;
        String mediaType;

        public ManifestEntry(String id, String href) {
            this(id, href, "application/xhtml+xml");
        }

        public ManifestEntry(String id, String href, String mediaType) {
            this.id = id;
            this.href = href;
            this.mediaType = mediaType;
        }

        public String getHref() {
            return href;
        }

        public String getId() {
            return id;
        }

        public String getMediaType() {
            return mediaType;
        }
    }
    List<ManifestEntry> manifest = new ArrayList<ManifestEntry>();
    Spine spine = new Spine();

    class Spine {

        List<String> ids = new ArrayList<String>();
        HashSet<Integer> contents = new HashSet<Integer>();

        public boolean add(String id, String content) {
            if (contents.contains(new Integer(content.hashCode())))
                return false;

            ids.add(id);
            int hash = content.hashCode();
            contents.add(new Integer(hash));

            return true;
        }

        public List<String> getIds() {
            return ids;
        }
    }

    public Opf() {
        manifest.add(new ManifestEntry("ncx", "toc.ncx", "application/x-dtbncx+xml"));
    }

    public void setLocale(Locale locale) {
        this.locale = locale;
    }

    public void setBookInfo(BookInfo bookInfo) {
        this.bookInfo = bookInfo;
    }

    /**
     * Add a page to the open packaging format file
     *
     * @id  the id of the page
     * @param name the name of the page
     * @return the href of the page, used in the toc
     */
    public boolean addPage(String id, String href, String content) {
        manifest.add(new ManifestEntry(id, href));
        return spine.add(id, content);
    }

    /**
     * Add a resource to the open packaging format file
     *
     * @param id the id of the resource
     * @param name the name of the resource
     * @return the href of the resource
     */
    public void addResource(String id, String name, String contentType) {
        manifest.add(new ManifestEntry(id, name, contentType));
    }

    /**
     * Write the content of the open packaging format file to the
     * output stream
     *
     * @param out the output stream
     * @throws IOException
     */
    public void write(OutputStream out) throws IOException, SAXException {
        ResourceBundle bundle = ResourceBundle.getBundle(OPF_BUNDLE, locale);
        ContentHandler handler = new PrintHandler(out);
        SaxUtil writer = new SaxUtil(handler);

        AttributesImpl atts = new AttributesImpl();
        writer.addAttribute(atts, "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
        writer.addAttribute(atts, "xmlns:dc", "http://purl.org/dc/elements/1.1/");
        writer.addAttribute(atts, "xmlns:opf", "http://www.idpf.org/2007/opf");
        writer.addAttribute(atts, "xmlns", "http://www.idpf.org/2007/opf");
        writer.addAttribute(atts, "version", "2.0");
        writer.addAttribute(atts, "unique-identifier", "nodes_id");

        writer.startElement("package", atts);

        // metadata
        writer.startElement("metadata");

        atts = new AttributesImpl();
        writer.addAttribute(atts, "xsi:type", "dcterms:RFC3066");

        writer.startElement("dc:language", atts);
        writer.characters("en-EN"); // fix for now
        writer.endElement("dc:language");

        writer.startElement("dc:title");
        writer.characters(title);
        writer.endElement("dc:title");

        atts = new AttributesImpl();
        writer.addAttribute(atts, "id", "nodes_id");

        writer.startElement("dc:identifier", atts);
        writer.characters(id);
        writer.endElement("dc:identifier");

        writer.startElement("dc:creator");
        writer.characters(bookInfo.getCreator());
        writer.endElement("dc:creator");

        if (bookInfo.getVersion() != null) {
            writer.startElement("dc:coverage");
            writer.characters(bookInfo.getVersion());
            writer.endElement("dc:coverage");
        }

        String pattern = bundle.getString("book.description");
        if (pattern != null) {
            MessageFormat formatter = new MessageFormat(pattern);
            String value = formatter.format(new Object[]{id});

            writer.startElement("dc:description");
            writer.characters(value);
            writer.endElement("dc:description");

            writer.endElement("metadata");
        }

        // manifest
        writer.startElement("manifest");
        Iterator<ManifestEntry> me = manifest.iterator();
        while (me.hasNext()) {
            ManifestEntry entry = me.next();

            atts = new AttributesImpl();
            writer.addAttribute(atts, "id", entry.getId());
            writer.addAttribute(atts, "href", entry.getHref());
            writer.addAttribute(atts, "media-type", entry.getMediaType());

            writer.startElement("item", atts);
            writer.endElement("item");
        }
        writer.endElement("manifest");

        // spine
        atts = new AttributesImpl();
        writer.addAttribute(atts, "toc", "ncx");

        writer.startElement("spine", atts);
        Iterator<String> se = spine.getIds().iterator();
        while (se.hasNext()) {
            String entry = se.next();

            atts = new AttributesImpl();
            writer.addAttribute(atts, "idref", entry);

            writer.startElement("itemref", atts);
            writer.endElement("itemref");
        }
        writer.endElement("spine");

        writer.startElement("guide");

        atts = new AttributesImpl();
        writer.addAttribute(atts, "href", manifest.get(1).getHref());
        writer.addAttribute(atts, "type", "cover");
        writer.addAttribute(atts, "title", "Cover");

        writer.startElement("reference", atts);
        writer.endElement("reference");

        writer.endElement("guide");

        writer.endElement("package");
    }

    /**
     * Has the title and the name been set?
     * 
     * @return
     */
    public boolean isInitialized() {
        return title != null && id != null;
    }

    public void setId(String id) {
        this.id = id;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}
